#pragma once

#include <functional>
#include <memory>

#include "tnet.h"
#include "connection.h"
#include "sockutil.h"

namespace tnet
{
    template<typename Derived>
    class BaseConnection : public nocopyable
                        , public std::enable_shared_from_this<Derived> 
    {
    public:
        typedef std::shared_ptr<Derived> DerivedConnPtr_t;
        typedef std::function<void (const DerivedConnPtr_t&, ConnEvent event, const string& )> RequestCallback_t;

        BaseConnection(const ConnectionPtr_t& conn, 
            const RequestCallback_t& callback = std::bind(&BaseConnection<Derived>::dummyCallback, _1, _2, _3),
            uint16_t length = 0);
        ~BaseConnection();

        int getSockFd() { return m_fd; }
        void send(const string& resp);

        //after is milliseconds
        void shutDown(int after = 0 );

        ConnectionPtr_t lockConn() { return m_conn.lock(); }
        WeakConnectionPtr_t getConn() { return m_conn; }

        RequestCallback_t getCallback() { return m_callback; }

        void onConnEvent(const ConnectionPtr_t& conn, ConnEvent event, const void* context);
    private:
        std::weak_ptr<Connection> m_conn;
        int m_fd;
        RequestCallback_t m_callback;
        uint16_t m_length;
       
        string   m_data;
        bool m_first;
        uint32_t m_dataSize;
        string m_remain;

        static void dummyCallback(DerivedConnPtr_t conn,ConnEvent event,const string&);
        void execute(const ConnectionPtr_t& conn, ConnEvent event, const void* context);
    }; 

    template<typename Derived>
    void BaseConnection<Derived>::dummyCallback(DerivedConnPtr_t conn,ConnEvent event,const string& context)
    {
    }    

    template<typename Derived>
    BaseConnection<Derived>::BaseConnection(const ConnectionPtr_t& conn, const RequestCallback_t& callback,uint16_t length)
        : m_conn(conn)
        , m_callback(callback)
        , m_length(length)
        , m_first(true)
        , m_dataSize(0) 
    {
        m_fd = conn->getSockFd();
    }

    template<typename Derived>
    BaseConnection<Derived>::~BaseConnection()
    {
    }

    template<typename Derived>
    void BaseConnection<Derived>::onConnEvent(const ConnectionPtr_t& conn, ConnEvent event, const void* context)
    {
        DerivedConnPtr_t tcpConn = this->shared_from_this();
        switch(event)
        {
            case Conn_ReadEvent:
                {
                    execute(conn,event,context);
                }
                break;   
			case Conn_WriteEvent:
            case Conn_CloseEvent:
            case Conn_ErrorEvent:
				{
                    m_callback(tcpConn, event, string());
				}
				break;    
            default:
                break; 
        }  
    }

    template<typename Derived>
    void BaseConnection<Derived>::execute(const ConnectionPtr_t& conn, ConnEvent event, const void* context)
    {
        DerivedConnPtr_t tcpConn = this->shared_from_this();
        const StackBuffer* buf = (const StackBuffer*)context;

        if( m_length > 0 )
        {
#if 0
            string data;
            if( m_data.size() == 0 )
            {
                if( buf->count > 0 && 
                    (buf->buffer[0] =='\n' || strncmp(buf->buffer,"\r\n",2) == 0 || strncmp(buf->buffer,"^]",2) == 0) )
                {
                    conn->shutDown();
                    return;
                }  
                data = m_remain;
                m_remain.clear();

                data.append(buf->buffer,buf->count);
                if(data.size() < m_length ) {
                    m_data = data;
                    return;
                }
                try {
                    m_dataSize = std::stoi( data.substr(0,m_length));
                }
                catch(const std::exception& e){
                    std::cerr << e.what() << '\n';
                    return;
                }
                data = data.substr(m_length);
            }
            else  {
                data = m_data;
                data.append(buf->buffer,buf->count);
            }

            if( data.size() >= m_dataSize )  {
                m_data = data.substr(0,m_dataSize);
                m_remain = data.substr(m_dataSize);
            }
            else  {
                m_data = data;
                m_remain.clear();
            }     

            if( m_data.size() != m_dataSize )
                return;
            m_dataSize = 0;
#else
            if(m_first)
            {
                m_data.clear();
                m_data.swap(m_remain);

                ///! Data legitimacy verification
                if( buf->count > 0 && 
                    (buf->buffer[0] =='\n' || strncmp(buf->buffer,"\r\n",2) == 0 || strncmp(buf->buffer,"^]",2) == 0) )
                {
                    conn->shutDown();
                    return;
                }  

                ///! Data length check
                if( buf->count < m_length )
                {
                    m_remain.append(buf->buffer,buf->count);
                    return;
                }

                ///! Parse header length information
                char header_buffer[128]={0};
                strncpy(header_buffer,buf->buffer,m_length-m_data.size());
                try
                {
                    m_dataSize = std::stoi(header_buffer);
                }
                catch(const std::exception& e)
                {
                    LOG_ERROR("parse request data exception %s",e.what());
                    conn->shutDown();
                    return;
                }
                
                uint32_t dataLen = buf->count-m_length+m_data.size();
                if( dataLen == m_dataSize )
                {
                    m_data.append(buf->buffer+m_length,dataLen);
                    m_first = true;
                    m_dataSize = 0;
                }
                else if(dataLen < m_dataSize)
                {
                    m_data.append(buf->buffer+m_length,dataLen);
                    m_first = false;
                    return;
                }
                else
                {
                    uint32_t aLen = dataLen - m_dataSize;
                    m_data.append(buf->buffer+m_length,m_dataSize);
                    m_remain.append(buf->buffer + m_length + m_dataSize, aLen);
                    m_first = true;
                    m_dataSize = 0;
                }
            }
            else
            {
                m_data.append(m_remain);
                m_remain.clear();
                if( m_data.size() + buf->count < m_dataSize)
                {
                    m_data.append(buf->buffer,buf->count);
                    m_first = false;
                    return;
                }
                else if( m_data.size() + buf->count == m_dataSize )
                {
                    m_data.append(buf->buffer,buf->count);
                    m_first = true;
                    m_dataSize = 0;
                }
                else
                {
                    uint32_t aLen = m_dataSize - buf->count;
                    m_data.append(buf->buffer,aLen);
                    m_remain.append(buf->buffer + aLen, buf->count - aLen);
                    m_first = true;
                    m_dataSize = 0;
                }
            }                       
#endif            
        }
        else
        {
            if( buf->count > 0 && 
                (buf->buffer[0] =='\n' || strncmp(buf->buffer,"\r\n",2) == 0 || strncmp(buf->buffer,"^]",2) == 0) )
            {
                conn->shutDown();
                return;
            } 
            m_data = string(buf->buffer,buf->count);
        }
        m_callback(tcpConn, event, m_data);
        m_data.clear();
    }

    template<typename Derived>
    void BaseConnection<Derived>::shutDown(int after)
    {
        ConnectionPtr_t conn = m_conn.lock();
        if(conn)
        {
            conn->shutDown(after);    
        }    
    }

    template<typename Derived>
    void BaseConnection<Derived>::send(const string& resp)
    {
        ConnectionPtr_t conn = m_conn.lock();
        if(!conn)
        {
            return;    
        }    
        if( m_length > 0 )
        {
            char buf[128] = { 0 };
            sprintf(buf,"%0*d",m_length,resp.size());
            string data(resp);
            data.insert(0,buf);
            conn->send(data);
            return;
        }
        conn->send(resp);
    }

    class TcpConnection;
    class TcpProxyConnection;

    typedef std::shared_ptr<TcpConnection> TcpConnectionPtr_t;
    typedef std::shared_ptr<TcpProxyConnection> ProxyConnectionPtr_t;

    class TcpConnection : public BaseConnection<TcpConnection>
    {
    public:
        TcpConnection(const ConnectionPtr_t& conn, const RequestCallback_t& callback,uint16_t length)
            : BaseConnection(conn,callback,length)
        {
        }
        ~TcpConnection(){};	
    };

    class TcpProxyConnection : public BaseConnection<TcpProxyConnection>
    {
    public:
        TcpProxyConnection(const ConnectionPtr_t& conn)
            : BaseConnection(conn)
            , m_isProxy(false)
            , m_custProxy(false)
        {
        }    

        TcpProxyConnection(const ConnectionPtr_t& conn, const RequestCallback_t& callback,uint16_t length)
            : BaseConnection(conn,callback,length)
            , m_isProxy(false)
            , m_custProxy(false)
        {
        }
        ~TcpProxyConnection(){};	

        inline void setSrcConn(const WeakConnectionPtr_t& conn) { m_srcConn = conn;};
        inline void setDestConn(const WeakConnectionPtr_t& conn) { m_destConn = conn;};
        inline void setProxy(bool on) { m_isProxy = on; };
        inline void setCustProxy(bool on) { m_custProxy = on; };

        void onConnEvent(const ConnectionPtr_t& conn, ConnEvent event, const void* context);
        int  createProxyConnection(const Address& addr);
        int  createProxyConnection(const string& un_path); 

        int  createProxyConnection(const Address& addr, const RequestCallback_t& callback);
        int  createProxyConnection(const string& un_path,const RequestCallback_t& Callback);
        bool doProxyConnection(const string& data);
    private:
        WeakConnectionPtr_t m_srcConn;
        WeakConnectionPtr_t m_destConn;
        bool m_isProxy;
        bool m_custProxy;
    };

    inline 
    void TcpProxyConnection::onConnEvent(const ConnectionPtr_t& conn, ConnEvent event, const void* context)
    {
        switch(event)
        {
            case Conn_ReadEvent:
                {
                    const StackBuffer* buf = (const StackBuffer*)context;
                    string data(buf->buffer,buf->count);
                    if( m_isProxy && m_custProxy )
                    {
                       getCallback()(shared_from_this(), event, data);
                       return;
                    }
                    doProxyConnection(data);
                }
                break;   
            case Conn_CloseEvent:
            case Conn_ErrorEvent:
                {
                    if(m_srcConn.lock()) return;
                    if(m_destConn.lock() ) {
                        ConnectionPtr_t c = m_destConn.lock();               
                        c->shutDown();
                    }
                    getCallback()(shared_from_this(), event, string());
                }
                break;    
            default:
                getCallback()(shared_from_this(), event, string());
                break; 
        }             
    }

    inline 
    int  TcpProxyConnection::createProxyConnection(const Address& addr)
    {
        int fd = SockUtil::create();
        ConnectionPtr_t conn = std::make_shared<Connection>(lockConn()->getIOLoop(), fd);
        ProxyConnectionPtr_t pConn = std::make_shared<TcpProxyConnection>(conn);
        conn->setEventCallback(std::bind(&TcpProxyConnection::onConnEvent, pConn, _1, _2, _3));
        pConn->setSrcConn(getConn());
        pConn->setProxy(true);
        m_destConn = WeakConnectionPtr_t(conn);
        return conn->connect(addr,1);
    }  

    inline 
    int  TcpProxyConnection::createProxyConnection(const string& un_path)
    {
        int fd = SockUtil::createUDS();
        ConnectionPtr_t conn = std::make_shared<Connection>(lockConn()->getIOLoop(), fd);
        ProxyConnectionPtr_t pConn = std::make_shared<TcpProxyConnection>(conn);              
        conn->setEventCallback(std::bind(&TcpProxyConnection::onConnEvent, pConn, _1, _2, _3));         
        pConn->setSrcConn(getConn());
        pConn->setProxy(true);
        this->setDestConn(WeakConnectionPtr_t(conn));
        return conn->connect(un_path,1);  
    }       

    inline 
    int  TcpProxyConnection::createProxyConnection(const Address& addr,const RequestCallback_t& callback)
    {
        int fd = SockUtil::create();
        ConnectionPtr_t conn = std::make_shared<Connection>(lockConn()->getIOLoop(), fd);
        ProxyConnectionPtr_t pConn = std::make_shared<TcpProxyConnection>(conn,callback,0);
        conn->setEventCallback(std::bind(&TcpProxyConnection::onConnEvent, pConn, _1, _2, _3));
        pConn->setSrcConn(getConn());
        pConn->setProxy(true);
        pConn->setCustProxy(true);
        m_destConn = WeakConnectionPtr_t(conn);
        return conn->connect(addr,1);
    }

    inline 
    int  TcpProxyConnection::createProxyConnection(const string& un_path,const RequestCallback_t& callback)
    {
        int fd = SockUtil::createUDS();
        ConnectionPtr_t conn = std::make_shared<Connection>(lockConn()->getIOLoop(), fd);
        ProxyConnectionPtr_t pConn = std::make_shared<TcpProxyConnection>(conn,callback,0);              
        conn->setEventCallback(std::bind(&TcpProxyConnection::onConnEvent, pConn, _1, _2, _3));         
        pConn->setSrcConn(getConn());
        pConn->setProxy(true);
        pConn->setCustProxy(true);
        this->setDestConn(WeakConnectionPtr_t(conn));
        return conn->connect(un_path,1);  
    }           

    inline 
    bool TcpProxyConnection::doProxyConnection(const string& data)
    {
        if( m_isProxy )
        {
            ConnectionPtr_t c = m_srcConn.lock();               
            c->send(data);
        }
        else
        {
            ConnectionPtr_t c = m_destConn.lock();    
            int n = SockUtil::getSockError(c->getSockFd());
            if( n != 0)
            {
                LOG_ERROR("SockError:%s",errorMsg(n) );
                lockConn()->shutDown(1);
                return false;
            }
            c->send(data);
        }
        return true;
    }         
}